using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using PropertyExpression.Common;

namespace PropertyExpression.ReactiveRelation
{
    public static partial class Value
    {
        public static IValue<TResult> Aggregate<TDataSetEntity, TArg, TResult>(
            this IPropertyValue<EntitySet<TDataSetEntity>> propertyValue,
            Func<IPropertyHolder<TDataSetEntity>, IValue<TArg>> func,
            Func<IAggregator<TArg, TResult>> aggregatorFunc)
            where TDataSetEntity : class
        {
            return new AggregateValue<TDataSetEntity, TArg, TResult>(propertyValue, func, aggregatorFunc);
        }

        private class AggregateValue<TDataSetEntity, TArg, TResult> : IValue<TResult> 
            where TDataSetEntity : class
        {
            private readonly IPropertyValue<EntitySet<TDataSetEntity>> propertyValue;
            private readonly Func<IPropertyHolder<TDataSetEntity>, IValue<TArg>> func;
            private readonly Func<IAggregator<TArg, TResult>> aggregatorFunc;

            public AggregateValue(IPropertyValue<EntitySet<TDataSetEntity>> propertyValue,
                Func<IPropertyHolder<TDataSetEntity>, IValue<TArg>> func, 
                Func<IAggregator<TArg, TResult>> aggregatorFunc)
            {
                this.propertyValue = propertyValue;
                this.func = func;
                this.aggregatorFunc = aggregatorFunc;
            }

            public IOptionalValue<TResult> GetValue(IPropertySettor propertySettor, 
                IOneToManyCollectionNewItemProcessor oneToManyCollectionNewItemProcessor)
            {
                return propertyValue.GetValue(propertySettor, oneToManyCollectionNewItemProcessor)
                    .ProcessValue(
                    entitySet =>
                        {
                            if (entitySet != default(EntitySet<TDataSetEntity>))
                            {
                                return propertyValue.ProcessOneToManyCollectionNewItem
                                    <Func<EntitySet<TDataSetEntity>, IEnumerable<TDataSetEntity>>>(
                                    newItem => set => set.Union(new[] {(TDataSetEntity) newItem}),
                                    newItem => set => set.Except(new[] {(TDataSetEntity) newItem}),
                                    () => set => set,
                                    propertySettor, oneToManyCollectionNewItemProcessor)
                                    .ProcessValue(
                                    setFunc =>
                                        {
                                            var aggregator = aggregatorFunc();
                                            foreach (var entity in setFunc(entitySet))
                                            {
                                                var closureEntity = entity;
                                                if (CreateValue(() => closureEntity)
                                                        .GetValue(propertySettor, oneToManyCollectionNewItemProcessor)
                                                        .Process(arg =>
                                                                     {
                                                                         aggregator.Process(arg);
                                                                         return true;
                                                                     },
                                                                 () => false
                                                        ) == false)
                                                {
                                                    return OptionalValue.Nothing<TResult>();
                                                }
                                            }
                                            return aggregator.Result.AsOptionalValue();
                                        }
                                    );
                            }
                            else
                            {
                                return aggregatorFunc().Result.AsOptionalValue();
                            }
                        }
                    );
            }

            public IEnumerable<ICell> GetCells()
            {
                return propertyValue.GetReverseCells().Concat(
                    CreateValue(() => { throw new ApplicationException(); }).GetCells()
                    );
            }

            private IValue<TArg> CreateValue(Func<TDataSetEntity> entityFunc)
            {
                return func(new EntityWrapper(entityFunc, this));
            }

            private class EntityWrapper : IEntityWrapper<TDataSetEntity>
            {
                private readonly Func<TDataSetEntity> entityFunc;
                private readonly AggregateValue<TDataSetEntity, TArg, TResult> parent;

                public EntityWrapper(Func<TDataSetEntity> entityFunc,
                    AggregateValue<TDataSetEntity, TArg, TResult> parent)
                {
                    this.entityFunc = entityFunc;
                    this.parent = parent;
                }

                public TDataSetEntity Entity
                {
                    get { return entityFunc(); }
                }

                public IEnumerable<object> GetReverseEntities(
                    object sender, IPreviousValueProcessor previousValueProcessor)
                {
                    return parent.propertyValue.GetReverseEntitiesForCollection(sender, previousValueProcessor);
                }

                public IPropertyValue<TProperty> GetPropertyValue<TProperty>(
                    IPropertyExpressionProxy<TDataSetEntity, TProperty> propertyExpressionProxy)
                {
                    return this.CreatePropertyValue(propertyExpressionProxy);
                }
            }
        }
    }
}
